package service

import (
	"crypto/sha1"
	"encoding/hex"
	"fmt"
	"shop/common/log"
	"shop/config"
	"shop/config/constant"
	"shop/dao"
	"shop/entity"
	"shop/utils/convertutil"
	"shop/utils/genutil"
	"shop/utils/httputil"
	"shop/utils/lockutil"
	"shop/utils/result"
	"strconv"
	"time"

	"github.com/cihub/seelog"
)

func GetUserOrderList(userId int64, pageNo, pageSize int) *result.Result {
	list := make([]*entity.OrderInfo, 0)
	db := dao.GetDB().Table(entity.TableNameOrderInfo).
		Where("user_id", userId).Where("create_time >= ?", time.Now().Unix()-int64(3600*24*30)).
		Order("create_time DESC").Offset((pageNo - 1) * pageSize).Limit(pageSize).
		Find(&list)
	if db.Error != nil {
		log.Errorf("fail to getUserOrderList, err: %v", db.Error)
	}
	for _, order := range list {
		order.ParseGoodsList() // 反序列化订单商品列表
	}
	return result.Ok(list)
}

func GetOrderDetail(orderNo string) *result.Result {
	order := &entity.OrderInfo{}
	db := dao.GetDB().Model(&order).Where("order_no", orderNo).Take(order)
	if order.OrderNo == "" && db.Error != nil {
		log.Warnf("order not exists, err:%v", db.Error)
		return result.Fail("order not exists")
	}
	order.ParseGoodsList() // 反序列化订单商品列表
	return result.Ok(order)
}

// GetOrderStatus 查询订单状态, 扫码支付时轮询用
func GetOrderStatus(orderNo string) *result.Result {
	status := 0
	err := dao.GetDB().Table(entity.TableNameOrderInfo).Select("order_status").Where("order_no", orderNo).Take(&status).Error
	if err != nil {
		seelog.Errorf("fail to search order_status from db, err: %v", err)
	}
	return result.Ok(status)
}

// 创建订单时的锁最大时间
const createOrderLockTime = 15 // second

func CreateOrder(params *entity.OrderInfo) (res *result.Result) {
	// check shopStatus
	shop := GetShopInfo(params.ShopId)
	if shop.Status != constant.ShopStatusNormal {
		return result.Fail("店铺未在营业, 当前状态:" + constant.ShopStatusCN(shop.Status))
	}
	if errMsg := checkCreateOrderParams(shop, params); errMsg != "" {
		return result.Fail("下单失败, 参数校验失败: " + errMsg)
	}

	// 锁用户id
	key := constant.CacheKeyPrefixLockCreateOrder + strconv.FormatInt(params.UserId, 10)
	defer lockutil.ReleaseLock(key, params.OrderNo)
	if !lockutil.TryLock(key, params.OrderNo, createOrderLockTime) {
		return result.Fail("存在未创建完的订单")
	}

	// 开始下单事务
	tx := dao.GetTx()
	defer func() {
		res = dao.CommitByResult(res, tx)
	}()
	params.TotalPrice = 0
	for i := range params.GoodsList {
		orderGoods := params.GoodsList[i]
		goodsInDB := &entity.Goods{}
		if err := dao.GetDB().Model(&goodsInDB).Where("id", orderGoods.GoodsId).Take(goodsInDB).Error; err != nil {
			log.Errorf("fail to get goods from db, err: %v", err)
			return result.Fail("获取商品失败")
		}
		if goodsInDB.Id == 0 {
			return result.Fail("商品不存在: " + orderGoods.Name)
		}
		goodsInDB.ParsePropertyList()

		// 校验订单商品列表
		errMsg := validPropertyList(orderGoods, goodsInDB)
		if errMsg != "" {
			log.Errorf("订单商品校验失败，err: %s", errMsg)
			return result.Fail(errMsg)
		}
		params.TotalPrice += int64(orderGoods.Price * orderGoods.Count)
		// 减库存
		if err := entity.UpdateGoodsStockSub(goodsInDB, orderGoods.PropertyMap, orderGoods.Count); err != nil {
			return result.Fail(fmt.Sprintf("减库存失败, %v, 商品:%s", err, goodsInDB.Name))
		}
		if err := tx.Exec("update goods set count=?, property_list=? where id=?", goodsInDB.Count, goodsInDB.PropertyListStr, goodsInDB.Id).Error; err != nil {
			log.Errorf("数据库更新失败，库存更新，更新property_list失败，err: %v", err)
			return result.Fail("库存更新失败")
		}
	}
	if params.TakeType == constant.TakeTypeTakeOut {
		if params.TotalPrice < int64(shop.SendingNeedPrice) {
			return result.Fail(fmt.Sprintf("不得低于起送价：%d", shop.SendingNeedPrice))
		}
		params.TotalPrice += int64(shop.SendingPrice)
		params.TotalPrice += int64(shop.PackingPrice)
	}
	params.OrderStatus = constant.OrderStatusNotPay
	params.CreateTime = time.Now().Unix()
	params.OrderNo = genutil.GenOrderNo(params.CreateTime, params.UserId) // gen orderNo
	params.GoodsListToStr()

	if err := tx.Model(&params).Create(&params).Error; err != nil {
		log.Errorf("新增订单失败, err:%v", tx.Error)
		return result.Fail("下单失败，数据插入异常")
	}
	log.Infof("订单创建成功, order: %+v", params)
	return result.Ok(params.OrderNo)
}

func checkCreateOrderParams(shop *entity.ShopInfo, params *entity.OrderInfo) string {
	// check parameters
	if len(params.GoodsList) == 0 {
		return "商品列表为空"
	}
	if params.UserId == 0 || params.ShopId == 0 {
		return "用户id或店铺id不能为0"
	}
	if params.TakeType == constant.TakeTypeTakeOut {
		if !shop.TakeOutEnable {
			return "不支持外卖配送"
		}
		// params.UserPhone = strings.TrimSpace(params.UserPhone)
		// params.AddressDetail = strings.TrimSpace(params.AddressDetail)
		// params.Receiver = strings.TrimSpace(params.Receiver)
		// if params.AddressDetail == "" || params.UserPhone == "" || params.Receiver == "" {
		// 	return "外卖配送时, 收货地址和联系电话和收货人不能为空"
		// }
	} else if params.TakeType == constant.TakeTypeOfflineTakeIn {
		if !shop.TakeInEnable {
			return "本店铺不支持到店自取"
		}
		// params.UserPhone = strings.TrimSpace(params.UserPhone)
		// params.AddressDetail = strings.TrimSpace(params.AddressDetail)
		// if params.UserPhone == "" || params.Receiver == "" {
		// 	return "联系电话和收货人不能为空"
		// }
	} else {
		return fmt.Sprintf("不支持的配送方式: %d", params.TakeType)
	}
	return ""
}

func validPropertyList(orderGoods *entity.OrderGoods, goodsInDB *entity.Goods) string {
	log.Infof("校验-订单商品属性, orderGoods: %+v, goodsInDB: %+v", convertutil.ToJsonString(orderGoods), convertutil.ToJsonString(goodsInDB))
	for _, propertyInDB := range goodsInDB.PropertyList {
		selectedOption := orderGoods.PropertyMap[propertyInDB.Name]
		if (propertyInDB.IsMain || propertyInDB.Required) && selectedOption == "" { // 必选属性
			return fmt.Sprintf("缺少必选属性，商品:%s, 属性名:%s", goodsInDB.Name, propertyInDB.Name)
		}

		var optionInDB *entity.GoodsPropertyOption
		for _, option := range propertyInDB.Options {
			if selectedOption == option.Name {
				optionInDB = option
				break
			}
		}
		if optionInDB == nil {
			return fmt.Sprintf("属性选项不存在, 商品:%s, 属性名:%s, 不存在的属性选项:%s", goodsInDB.Name, propertyInDB.Name, selectedOption)
		}
		if optionInDB.Disabled {
			return fmt.Sprintf("该选项已经下架, 商品:%s, 属性名:%s, 选项:%s", goodsInDB.Name, propertyInDB.Name, optionInDB.Name)
		}
		if propertyInDB.IsMain && (optionInDB.Count == 0 || orderGoods.Count > optionInDB.Count) {
			return fmt.Sprintf("该规格商品库存不够, 商品:%s, 属性名:%s, 选项:%s", goodsInDB.Name, propertyInDB.Name, optionInDB.Name)
		}

		if propertyInDB.IsMain {
			orderGoods.Price = optionInDB.Price
		}
	}
	return ""
}

// ConfirmFinishPay 初步确认支付
func ConfirmFinishPay(orderNo string, userId int64) *result.Result {
	tx := dao.GetTx().Table(entity.TableNameOrderInfo).
		Where("order_no=? and user_id=? and order_status=?", orderNo, userId, constant.OrderStatusNotPay).
		UpdateColumn("order_status", constant.OrderStatusWaitTake) // 这里支付后就把订单状态改为待收货
	return result.Ok(tx.Commit().RowsAffected)
}

// CancelOrder 取消并删除订单
func CancelOrder(orderNo string, userId int64) (res *result.Result) {
	tx := dao.GetTx()
	defer func() {
		res = dao.CommitByResult(res, tx)
	}()
	err := tx.Table(entity.TableNameOrderInfo).
		Where("order_no=? and user_id=? and order_status=?", orderNo, userId, constant.OrderStatusNotPay).
		Delete(nil).Error
	if err != nil {
		log.Errorf("fail to update order_status, err: %v", err)
		return result.Fail("订单取消失败")
	}

	resp := GetOrderDetail(orderNo)
	if resp.Code != result.ErrCodeOk {
		return res
	}

	for _, orderGoods := range resp.Data.(*entity.OrderInfo).GoodsList {
		goodsInDB := GetGoodsById(orderGoods.GoodsId)
		// 将库存加回去
		if err := entity.UpdateGoodsStockAdd(goodsInDB, orderGoods.PropertyMap, orderGoods.Count); err != nil {
			return result.Fail(err.Error())
		}
		if err := tx.Exec("update goods set count=?, property_list=? where id=?", goodsInDB.Count, goodsInDB.PropertyListStr, goodsInDB.Id).Error; err != nil {
			log.Errorf("数据库更新失败，库存更新，更新property_list失败，err: %v", err)
			return result.Fail("库存更新失败")
		}
	}
	return result.Ok(nil)
}

func ConfirmReceiveAndAddGoodsSoldCount(orderNo string, userId int64) (res *result.Result) {
	orderDetailRes := GetOrderDetail(orderNo)
	if orderDetailRes.Code != result.ErrCodeOk {
		return orderDetailRes
	}
	orderInfo := orderDetailRes.Data.(*entity.OrderInfo)
	if orderInfo.OrderStatus != constant.OrderStatusWaitTake {
		return result.Fail("确认收货失败, 当前订单状态:%s" + constant.GoodsStatusCN(int(orderInfo.OrderStatus)))
	}

	tx := dao.GetTx()
	defer func() {
		res = dao.CommitByResult(res, tx)
	}()
	// 增加商品的已售数量
	for _, goods := range orderInfo.GoodsList {
		if err := tx.Exec("update goods set sold_count=sold_count+? where id=?", goods.Count, goods.GoodsId).Error; err != nil {
			return result.Fail("fail to update goods.sold_count: " + err.Error())
		}
	}
	err := tx.Table(entity.TableNameOrderInfo).
		Where("order_no=? and user_id=?", orderNo, userId).
		Updates(&entity.OrderInfoFilter{
			OrderStatus: convertutil.Int8Ptr(constant.OrderStatusFinished),
			FinishTime:  convertutil.Int64Ptr(time.Now().Unix()),
		}).Error
	if err != nil {
		return result.Fail("fail to update order status: " + err.Error())
	}
	return result.Ok(nil)
}

type XinyeyunPrintOrderParams struct {
	Sn        string `json:"sn"`      // 打印机编号
	Content   string `json:"content"` // 不能超过 4096 字节
	Copies    int    `json:"copies"`  // 打印多少份
	User      string `json:"user"`
	Timestamp string `json:"timestamp"`
	Sign      string `json:"sign"`
}

type XinyeyunPrintOrderResponse struct {
	Msg                string `json:"msg"`
	Code               int    `json:"code"`
	Data               string `json:"data"`
	ServerExecutedTime int    `json:"serverExecutedTime"`
}

// PrinterOrder 打印订单信息小票（打印机）
func PrinterOrder(orderNo string) *result.Result {
	resOrder := GetOrderDetail(orderNo)
	if resOrder.Code != result.ErrCodeOk {
		return resOrder
	}
	order := resOrder.Data.(*entity.OrderInfo)
	log.Infof("orderInfo: %+v", order)
	shop := GetShopInfo(order.ShopId)
	if shop == nil || shop.Id == 0 {
		return result.Fail("店铺不存在")
	}
	merchant := config.GetMerchantConfByShopId(order.ShopId)
	if merchant == nil || merchant.ShopId == 0 {
		return result.Fail("商家不存在或已下线")
	}

	// 打印小票
	params := XinyeyunPrintOrderParams{
		Sn:        merchant.PrinterTicket,
		Copies:    1,
		User:      config.Conf.XinyeyunUsername,
		Timestamp: strconv.FormatInt(time.Now().Unix(), 10),
	}
	signByte := sha1.Sum([]byte(config.Conf.XinyeyunUsername + config.Conf.XinyeyunApiKey + params.Timestamp))
	params.Sign = hex.EncodeToString(signByte[:])
	// 生成小票的订单信息
	goodsListStr := ""
	order.ParseGoodsList()
	for _, goods := range order.GoodsList {
		propertyStr := ""
		for _, p := range goods.PropertyMap {
			if len(propertyStr) > 0 {
				propertyStr += ","
			}
			propertyStr += p
		}
		if len(propertyStr) > 0 {
			goodsListStr += fmt.Sprintf("%s[%s]  ￥%d x%d \n", goods.Name, propertyStr, goods.Price/100, goods.Count)
		} else {
			goodsListStr += fmt.Sprintf("%s  ￥%d x%d \n", goods.Name, goods.Price/100, goods.Count)
		}
	}
	if order.TakeType == constant.TakeTypeTakeOut {
		goodsListStr += fmt.Sprintf("打包费    ￥%d \n", shop.PackingPrice/100)
		goodsListStr += fmt.Sprintf("配送费    ￥%d \n", shop.SendingPrice/100)
	}
	if order.TakeType == constant.TakeTypeTakeOut {
		params.Content = fmt.Sprintf(`
<C><IMG160></IMG>
<CB>**%s**
-----------------
下单时间:%s
系统订单号:%s
微信交易号:%s
***商品列表***
%s
-----------------
<B>订单总价:￥%.2f
<B>实际支付:￥%.2f
<BOLD>地址:%s
<BOLD>收货人:%s
订单备注:%s
		`, shop.Name,
			convertutil.FormatDateChinaDate(time.Unix(order.CreateTime, 0)),
			order.OrderNo,
			order.WxPayTransactionId,
			goodsListStr,
			float64(order.TotalPrice)/100,
			float64(order.PayPrice)/100,
			order.AddressDetail,
			order.Receiver+", "+order.UserPhone,
			order.ExtraInfo,
		)
	} else {
		params.Content = fmt.Sprintf(`
<C><IMG160></IMG>
<CB>**%s**
--------------------------------
下单时间: %s
系统订单号: %s
微信交易号：%s
***商品列表***
%s
--------------------------------
<B>订单总价：￥%.2f
<B>实际支付：￥%.2f
订单备注：%s
		`, shop.Name,
			convertutil.FormatDateChinaDate(time.Unix(order.CreateTime, 0)),
			order.OrderNo,
			order.WxPayTransactionId,
			goodsListStr,
			float64(order.TotalPrice)/100,
			float64(order.PayPrice)/100,
			order.ExtraInfo,
		)
	}

	resp := &XinyeyunPrintOrderResponse{}
	convertutil.ParseJson(httputil.PostJson("https://open.xpyun.net/api/openapi/xprinter/print", params), resp)
	if resp.Code != 0 {
		log.Infof("[打印订单] 小票打印失败, params:%+v, resp=%+v", params, resp)
	}

	// 打印标签
	params = XinyeyunPrintOrderParams{
		Sn:        merchant.PrinterLabel,
		Copies:    1,
		User:      config.Conf.XinyeyunUsername,
		Timestamp: strconv.FormatInt(time.Now().Unix(), 10),
	}
	signByte = sha1.Sum([]byte(config.Conf.XinyeyunUsername + config.Conf.XinyeyunApiKey + params.Timestamp))
	params.Sign = hex.EncodeToString(signByte[:])
	// 每个商品打印一个标签
	order.ParseGoodsList()
	for _, goods := range order.GoodsList {
		propertyStr := ""
		for _, p := range goods.PropertyMap {
			if len(propertyStr) > 0 {
				propertyStr += ","
			}
			propertyStr += p
		}
		for i := 0; i < goods.Count; i++ {
			params.Content += fmt.Sprintf(`
			<PAGE>
			<TEXT x="10" y="10" w="1" h="1" r="0">%s</TEXT>
			<TEXT x="10" y="45" w="1" h="1" r="0">%s</TEXT>
			<TEXT x="10" y="80" w="1" h="1" r="0">[%s]</TEXT>
			</PAGE>
			`,
				shop.Name,
				goods.Name,
				propertyStr,
			)
		}
	}
	resp = &XinyeyunPrintOrderResponse{}
	convertutil.ParseJson(httputil.PostJson("https://open.xpyun.net/api/openapi/xprinter/printLabel", params), resp)
	if resp.Code != 0 {
		log.Infof("[打印订单] 标签打印失败, params:%+v, resp=%+v", params, resp)
	}
	return result.Ok(resp.Data)
}
